package me.bincker.formwork.web.core.controller

import me.bincker.formwork.web.core.exception.WebException
import org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController
import org.springframework.boot.web.servlet.error.ErrorAttributes
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Component
import org.springframework.util.StringUtils
import org.springframework.validation.BindingResult
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.context.request.RequestAttributes
import org.springframework.web.context.request.ServletWebRequest
import org.springframework.web.context.request.WebRequest
import org.springframework.web.servlet.HandlerExceptionResolver
import org.springframework.web.servlet.ModelAndView
import java.lang.Exception
import java.util.*
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import kotlin.collections.HashMap

/**
 * 异常消息控制器
 */
@RestController
@RequestMapping("\${server.error.path:\${error.path:/error}}")
class ErrorController(val errorAttributes: ErrorAttributes) : AbstractErrorController(errorAttributes) {

    companion object{
        const val ERROR_PATH = "/error"
        private val ERROR_ATTRIBUTE = "${CustomErrorAttributes::class.qualifiedName}.ERROR"

        @Component
        class CustomErrorAttributes : ErrorAttributes, HandlerExceptionResolver{
            override fun resolveException(request: HttpServletRequest, response: HttpServletResponse, handler: Any?, exception: Exception): ModelAndView? {
                request.setAttribute(ERROR_ATTRIBUTE, exception)
                return null
            }

            override fun getError(webRequest: WebRequest) =
                    (
                            getAttribute(webRequest, ERROR_ATTRIBUTE) ?:
                            getAttribute(webRequest, "javax.servlet.error.exception")
                            ) as Throwable?

            override fun getErrorAttributes(webRequest: WebRequest, includeStackTrace: Boolean): MutableMap<String, Any> {
                val error = getError(webRequest)
                val errorAttributes = HashMap<String, Any>()
                errorAttributes["timestamp"] = Date()
                errorAttributes["path"] = getAttribute(webRequest, "javax.servlet.error.request_uri") ?: ""
                if(error != null){
                    if(error is WebException){
                        errorAttributes["status"] = error.httpStatus.value()
                        errorAttributes["message"] = error.responseMessage
                    }else{
                        errorAttributes["status"] = getAttribute(webRequest, "javax.servlet.error.status_code") ?: -1
                        val message = getAttribute(webRequest, "javax.servlet.error.message")
                        if ((!StringUtils.isEmpty(message) || errorAttributes["message"] == null) && error !is BindingResult) {
                            errorAttributes["message"] = if (StringUtils.isEmpty(message)) "No message available" else message!!
                        }
                    }
                }
                return errorAttributes
            }

            private fun getAttribute(requestAttributes: RequestAttributes, name: String) = requestAttributes.getAttribute(name, 0)
        }
    }

    override fun getErrorPath() = ERROR_PATH

    override fun getStatus(request: HttpServletRequest): HttpStatus {
        val error = errorAttributes.getError(ServletWebRequest(request))
        if(error != null && error is WebException){
            return error.httpStatus
        }
        return HttpStatus.valueOf(request.getAttribute("javax.servlet.error.status_code") as Int? ?: 500)
    }

    @RequestMapping
    fun error(request: HttpServletRequest): ResponseEntity<Map<String, Any>> {
        val body = this.getErrorAttributes(request, true)
        val status = this.getStatus(request)
        return ResponseEntity(body, status)
    }
}